package pers.kw.config.oauth;

import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * OAuth配置
 */
@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private final PasswordEncoder passwordEncoder;
    private final AuthenticationManager authenticationManager;
    private final UserDetailsService userDetailsService;
    private final RedisClientDetailsService redisClientDetailsService;
    private final RedisAuthorizationCodeServices redisAuthorizationCodeServices;
    private final OAuth2ResponseExceptionTranslator oAuth2ResponseExceptionTranslator;

    @Autowired
    public AuthorizationServerConfig(
            AuthenticationManager authenticationManager,
            PasswordEncoder passwordEncoder,
            UserDetailsService userDetailsService,
            RedisClientDetailsService redisClientDetailsService,
            RedisAuthorizationCodeServices redisAuthorizationCodeServices,
            OAuth2ResponseExceptionTranslator oAuth2ResponseExceptionTranslator) {
        this.passwordEncoder = passwordEncoder;
        this.authenticationManager = authenticationManager;
        this.userDetailsService = userDetailsService;
        this.redisClientDetailsService = redisClientDetailsService;
        this.redisAuthorizationCodeServices = redisAuthorizationCodeServices;
        this.oAuth2ResponseExceptionTranslator = oAuth2ResponseExceptionTranslator;
    }

    @Bean
    public TokenEnhancer tokenEnhancer() {
        return (accessToken, authentication) -> {
            //todo 类型转换异常
            //OAuthUser principal = (OAuthUser) authentication.getUserAuthentication().getPrincipal();
            Object principal = authentication.getUserAuthentication().getPrincipal();
            Map<String, Object> additionalInfo = new HashMap<>(16);
            if (principal != null) {
                additionalInfo.put("email", "1357760345@qq.com");
                additionalInfo.put("sex", "男");
                System.out.println("principal：\n" + JSON.toJSONString(principal,
                        true));
            }
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            return accessToken;
        };
    }

    /**
     * 使用密钥来编码jwt中的OAuth2令牌
     *
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        //可以采用属性注入方式
        //非对称加密如何使用
        jwtAccessTokenConverter.setSigningKey("kw");
        return jwtAccessTokenConverter;
    }

    /**
     * 使用jwt方式生成令牌
     * header.payload.sign
     *
     * @return
     */
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * (基于内存)用来配置可以给哪些客户端进行授权
     *
     * @param clients 客户端信息配置
     */
  /*  @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //内存方式配置
        clients.inMemory()
                //客户端id
                .withClient("ec")
                //客户端密钥
                //密钥必须进行加密，否则会报错
                //控制台打印：Encoded password does not look like BCrypt，请求返回401
                .secret(passwordEncoder.encode("ec"))
                //重定向的地址
                .redirectUris("http://127.0.0.1:9011/callback.html")
                //.redirectUris("https://blog.csdn.net/qq_36762765")
                //配置授权服务支持的授权模式
                //"authorization_code", "refresh_token","implicit","password","client_credentials"
                .authorizedGrantTypes("authorization_code", "refresh_token")
                //令牌允许获取的资源权限
                .scopes("all")
                //是否开启自动授权
                .autoApprove(true).and()
                //客户端id
                .withClient("product")
                //客户端密钥
                .secret(passwordEncoder.encode("product"))
                //重定向的地址
                .redirectUris("http://127.0.0.1:9022/callback.html")
                //配置授权服务支持的授权模式
                .authorizedGrantTypes("authorization_code", "refresh_token")
                //令牌允许获取的资源权限
                .scopes("all")
                //是否开启自动授权
                .autoApprove(true);

    }
*/

    /**
     * (基于mysql+redis)用来配置可以给哪些客户端进行授权
     *
     * @param clients 客户端信息配置
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(redisClientDetailsService);
        redisClientDetailsService.loadAllClientToCache();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //刷新token需要
        endpoints.userDetailsService(userDetailsService);
        //认证管理器
        endpoints.authenticationManager(authenticationManager);
        //支持获取token的请求方式
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);


        //token增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), jwtAccessTokenConverter()));

        //配置令牌存储为jwt令牌存储
        endpoints.tokenStore(tokenStore());
        endpoints.tokenEnhancer(tokenEnhancerChain);

        //授权码存储到redis
        endpoints.authorizationCodeServices(redisAuthorizationCodeServices);

        //异常信息处理
        endpoints.exceptionTranslator(oAuth2ResponseExceptionTranslator);

    }

    /**
     * 授权服务安全认证配置
     *
     * @param security 安全认证配置
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //允许表单认证，allowFormAuthenticationForClients=true
        //allowFormAuthenticationForClients=false时，通过basic auth认证，kw:kw
        security.allowFormAuthenticationForClients();
    }
}
